﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.Samples.ServiceHosting.StorageClient;
using Ciripescu_WebRole.Entities;
using System.Data.Services.Client;

namespace Ciripescu_WebRole.BL
{
    public class CiripManager
    {
        public static int NbOfCiripsPerPage = Properties.Settings.Default.NbOfCiripsPerPage;
        #region Public Methods

        public CiripManager()
        {
            m_svcContext             = new MyServiceContext();
            m_svcContext.RetryPolicy = RetryPolicies.RetryN(3, TimeSpan.FromSeconds(1));
        }

        public IEnumerable<Cirip> DoQuery(IQueryable<Cirip> q)
        {
            TableStorageDataServiceQuery<Cirip> query = new TableStorageDataServiceQuery<Cirip>(q as DataServiceQuery<Cirip>);
            IEnumerable<Cirip> queryResults           = query.ExecuteAllWithRetries();
            return queryResults;
        }

        public IEnumerable<Cirip> DoQuery(IQueryable<Cirip> q, int count)
        {
            //TableStorageDataServiceQuery<Cirip> query = new TableStorageDataServiceQuery<Cirip>(q as DataServiceQuery<Cirip>);
            IEnumerable<Cirip> queryResults = q.Take(count);
            return queryResults;
        }

        public Cirip GetSingleCirip(string userName,string rowKey)
        {
            try
            {
                return (DoQuery(from c in m_svcContext.Cirip where c.PartitionKey == userName && c.RowKey == rowKey select c)).Single();
            }
            catch (InvalidOperationException ex)
            {
                // no items to return
                return null;
            }
            catch (Exception e)
            {
                Utils.WriteLog(EventLogName.Error, e.ToString());
                return null;
            }
        }

        
        /// <summary>
        /// Returns the last NbOfCiripsPerPage cirips said by a certain user
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public IEnumerable<Cirip> GetByUserName(string userName)
        {
            return GetByUserName(userName, string.Empty);
        }
        public IEnumerable<Cirip> GetAllItems()
        {
            var results = from c in m_svcContext.Cirip select c;
            return DoQuery(results);
        }
        /// <summary>
        /// Get the last NbOfCiripsPerPage said by a user 
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="startFromRowKey"></param>
        /// <returns></returns>
        public IEnumerable<Cirip> GetByUserName(string userName, string startFromRowKey)
        {
            IQueryable<Cirip> results = null;
            if (string.IsNullOrEmpty(startFromRowKey))
                results = from c in m_svcContext.Cirip where c.PartitionKey == userName select c;
            else
                results = from c in m_svcContext.Cirip where c.PartitionKey == userName && c.RowKey.CompareTo(startFromRowKey) > 0 select c;
            return DoQuery(results,NbOfCiripsPerPage);
        }

        /// <summary>
        /// Gets the total number of cirips for a single user.
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public int GetNrOfCirips(string userName)
        {
            var results = from c in m_svcContext.Cirip where c.PartitionKey == userName select c;
            IEnumerable<Cirip> list = DoQuery(results);
            return list.Count<Cirip>();
        }

        public Cirip GetFirst(string userName)
        {
            try
            {
                return (from c in m_svcContext.Cirip where c.PartitionKey == userName select c).First();
            }
            catch (ArgumentNullException e)
            {
                Utils.WriteLog(EventLogName.Error, e.ToString());
                return null;
            }
            catch (InvalidOperationException)
            {
                // no items to return
                return null;
            }
        }
        
        /*
        /// <summary>
        /// Gets a sorted list of ALL replies for a user, that are between 2 time frames. If either startFromRowKey, endToRowKey or both are not specified they will not be taken into account
        /// ALL are needed because the output is not sorted by timestamp
        /// but by userName (partition key) and then timestamp (rowkey). For instance the first item is the youngest, but not from the whole list, 
        /// only from it's partition. Sorting is needed in the end.
        /// </summary>        
        public List<Cirip> GetRepliesForUserName(string userName,string startFromRowKey, string endToRowKey)
        {
            IQueryable<Cirip> results = null;
            if (!string.IsNullOrEmpty(startFromRowKey) && !string.IsNullOrEmpty(endToRowKey))
                results = from c in m_svcContext.Cirip where c.InReplyToUser == userName && c.RowKey.CompareTo(startFromRowKey) > 0 && c.RowKey.CompareTo(endToRowKey) < 0 select c;
            else if (string.IsNullOrEmpty(startFromRowKey) && !string.IsNullOrEmpty(endToRowKey))
                results = from c in m_svcContext.Cirip where c.InReplyToUser == userName && c.RowKey.CompareTo(endToRowKey) < 0 select c;
            else if (!string.IsNullOrEmpty(startFromRowKey) && string.IsNullOrEmpty(endToRowKey))
                results = from c in m_svcContext.Cirip where c.InReplyToUser == userName && c.RowKey.CompareTo(startFromRowKey) > 0 select c;
            else
                results = from c in m_svcContext.Cirip where c.InReplyToUser == userName select c;

            List<Cirip> listaReplies = DoQuery(results).ToList();
            listaReplies.Sort(delegate(Cirip c1, Cirip c2) { return c2.Timestamp.CompareTo(c1.Timestamp); });
            return listaReplies;
        }*/

        /// <summary>
        /// Returns all cirips that a user should see (from all those he follows)
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public IEnumerable<Cirip> GetNewCiripsForUserName(string userName)
        {
            return GetNewCiripsForUserName(userName, string.Empty);
        }

        /// <summary>
        /// Returns the cirips done said by a given username
        /// </summary>
        /// <param name="userName">The user that wrote the cirips</param>
        /// <param name="currentPage">0 if all cirips are needed or a page number if only one page of cirips should be returned</param>
        /// <returns></returns>
        public List<Cirip> GetCiripsFromUserName(string userName, int currentPage)
        {
            var results = from c in m_svcContext.Cirip where c.PartitionKey == userName select c;

            if (currentPage <= 0)
                return DoQuery(results).ToList();

            List<Cirip> ret = DoQuery(results).ToList();
            // verifica daca exista pagina
            if (ret.Count > (currentPage - 1) * NbOfCiripsPerPage)
            {
                if (NbOfCiripsPerPage*currentPage>ret.Count) // este ultima pagina
                    return ret.GetRange(NbOfCiripsPerPage * (currentPage - 1), ret.Count - NbOfCiripsPerPage * (currentPage-1));
                else // pagina oarecare
                    return ret.GetRange(NbOfCiripsPerPage * (currentPage - 1), NbOfCiripsPerPage);
            }
            //
            else return new List<Cirip>();// daca s-a cerut pagina care nu exista nu intoarce nimic
        }

        
        /*
        /// <summary>
        /// Returns all (first NbOfCiripsPerPage) cirips that a user should see (from all those he follows)
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="startFromRowKey">All cirips returned will have an older RowKey then this one</param>
        /// <returns></returns>
        public IEnumerable<Cirip> GetNewCiripsForUserName(string userName,string startFromRowKey)
        {
            List<Cirip> lista = new List<Cirip>();

            try
            {
                // adauga in primul rand cirip-urile proprii - fiind dintr-o singur partitie tot lista e deja sortata ok
                lista.AddRange(GetByUserName(userName, startFromRowKey).ToList());

                UrmarireManager fm = new UrmarireManager();
                // lista cu toti userii pe care ii urmareste userName
                List<Urmarire> listOfFollowedUsers = fm.GetFollowedUsers(userName).ToList();
                if (listOfFollowedUsers.Count > 0)
                {
                    for (int i = 0; i < listOfFollowedUsers.Count; i++)
                    {
                        IQueryable<Cirip> results = null;
                        if (string.IsNullOrEmpty(startFromRowKey))
                            results = from cirip in m_svcContext.Cirip where cirip.PartitionKey == listOfFollowedUsers[i].getFollowdUser() && cirip.InReplyToUser != userName select cirip;
                        else
                            results = from cirip in m_svcContext.Cirip where cirip.PartitionKey == listOfFollowedUsers[i].getFollowdUser() && cirip.InReplyToUser != userName && cirip.RowKey.CompareTo(startFromRowKey) > 0 select cirip;
                        lista.AddRange(DoQuery(results, NbOfCiripsPerPage));
                    }

                    // sorteaza dupa timestamp
                    lista.Sort(delegate(Cirip c1, Cirip c2) { return c2.Timestamp.CompareTo(c1.Timestamp); });
                }

                // adauga si reply-urile
                // din cauza ca automat se sorteaza dupa partitionKey si apoi RowKey suntem obligati fie sa interogam fiecare user din
                // baza de date (no way!), fie sa luam toate replieurile si sa le sortam noi manual
                // si cum nu vrem sa ne trezim cu 10 mii de reply-uri care trebuie toate sortate din cod vom cere explicit sa nu primim
                // reply-uri mai vechi decat ultimul cirip din lista deja sortata si limitata la NbOfCiripsPerPage de mai sus
                string endToRowKey = null;
                if (lista.Count >= NbOfCiripsPerPage)
                {
                    lista = lista.GetRange(0, NbOfCiripsPerPage); // scurtam lista
                    endToRowKey = lista[NbOfCiripsPerPage - 1].RowKey; // luam ultimul key din ea
                }
                else
                    endToRowKey = string.Empty;
                

                List<Cirip> listaReplies = GetRepliesForUserName(userName, startFromRowKey, endToRowKey);
                // ce am primit mai sus desi pot sa fie foarte multe daca cineva ne floodeaza intentionat toate sunt mai noi decat ultimul cirip din 
                // lista de pana acuma deci sunt candidati reali la afisare :)
                
                // scurteaza lista sa nu mai sortam degeaba o mie reply-uri pe langa restul de NbOfCiripsPerPage ciripuri
                if (listaReplies.Count > NbOfCiripsPerPage)
                    listaReplies = listaReplies.GetRange(0, NbOfCiripsPerPage);
                // reuneste listele si sorteaza din nou
                lista.AddRange(listaReplies);
                lista.Sort(delegate(Cirip c1, Cirip c2) { return c2.Timestamp.CompareTo(c1.Timestamp); });
                // mai curatam odata (acuma cred ca sunt 2*NbOfCiripsPerPage)
                if (lista.Count > NbOfCiripsPerPage)                
                    lista = lista.GetRange(0, NbOfCiripsPerPage);
                return lista;
            }
            catch (Exception ex)
            {
                Utils.WriteLog(EventLogName.Error, ex.ToString());
                return null;
            }
        }*/

        // Codul de sus merge dar cred ca este o metoda absolut imbecila si de complexitate inutila pentru a face exact ce face
        // metoda de sub folosid doar o tabela in plus si de 10 ori mai putine query-uri
        // Normalization IS EVIL!!!

        /// <summary>
        /// Returns all (first NbOfCiripsPerPage) cirips that a user should see (from all those he follows)
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="startFromRowKey">All cirips returned will have an older RowKey then this one</param>
        /// <returns></returns>
        public IEnumerable<Cirip> GetNewCiripsForUserName(string userName, string startFromRowKey)
        {
            List<Cirip> lista = new List<Cirip>();
            
            // cand am scris chestia asta numai eu si Dumnezeu stiam ce fac
            // acuma numai Dumnezeu stie :(

            try
            {
                // adauga in primul rand cirip-urile proprii - fiind dintr-o singur partitie toata lista e deja sortata ok
                lista.AddRange(GetByUserName(userName, startFromRowKey).ToList());

                UrmarireManager fm = new UrmarireManager();
                // lista cu toti userii pe care ii urmareste userName
                List<Urmarire> listOfFollowedUsers = fm.GetFollowedUsers(userName).ToList();
                
                if (listOfFollowedUsers.Count > 0)
                {
                    // Asta ii O(infricosator), dar pe Azure merge bine, si oricum nu am ce face
                    // sa fie clar ca pe o baza de date relationala nu as fi facut asa ceva - un join si order by era suficient
                    for (int i = 0; i < listOfFollowedUsers.Count; i++)
                    {
                        IQueryable<Cirip> results = null;
                        if (string.IsNullOrEmpty(startFromRowKey))
                            results = from cirip in m_svcContext.Cirip where cirip.PartitionKey == listOfFollowedUsers[i].getFollowdUser() && cirip.InReplyToUser != userName select cirip;
                        else
                            results = from cirip in m_svcContext.Cirip where cirip.PartitionKey == listOfFollowedUsers[i].getFollowdUser() && cirip.InReplyToUser != userName && cirip.RowKey.CompareTo(startFromRowKey) > 0 select cirip;
                        lista.AddRange(DoQuery(results, NbOfCiripsPerPage));
                    }

                    // sorteaza dupa timestamp
                    lista.Sort(delegate(Cirip c1, Cirip c2) { return c2.TimePosted.CompareTo(c1.TimePosted); });
                }

                if (lista.Count >= NbOfCiripsPerPage)
                    lista = lista.GetRange(0, NbOfCiripsPerPage); // scurtam lista                    

                // adauga si reply-urile
                CiripMessageManager msgManager = new CiripMessageManager();

                List<Cirip> listaReplies = msgManager.CiripMessageListToCiripList(msgManager.GetForUserName(userName, 1, startFromRowKey),1);
                                
                // reuneste listele si sorteaza din nou
                lista.AddRange(listaReplies);
                lista.Sort(delegate(Cirip c1, Cirip c2) { return c2.TimePosted.CompareTo(c1.TimePosted); });
                // nu cred ca merita sa mai curatam odata (acuma cred ca sunt maxim 2*NbOfCiripsPerPage)
                //if (lista.Count > NbOfCiripsPerPage)
                //    lista = lista.GetRange(0, NbOfCiripsPerPage);
                return lista;
            }
            catch (Exception ex)
            {
                Utils.WriteLog(EventLogName.Error, ex.ToString());
                return null;
            }
        }

        public void Delete(Cirip itemToDelete)
        {
            m_svcContext.DeleteObject(itemToDelete);
            m_svcContext.SaveChanges();
        }

        
        public void DeleteWithoutQ(Cirip itemToDelete)
        {
            m_svcContext.AttachTo(m_svcContext.CiripTableName, itemToDelete, "*");
            Delete(itemToDelete);
        }

        /// <summary>
        /// Updates an existing item from the tables.
        /// </summary>
        /// <param name="item"></param>
        public void Save(Cirip item)
        {
            m_svcContext.UpdateObject(item);
            m_svcContext.SaveChanges();
        }

        /// <summary>
        /// Insert a new item in the tables.
        /// </summary>
        /// <param name="newItem"></param>
        public void Insert(Cirip newItem)
        {
            m_svcContext.AddObject(m_svcContext.CiripTableName, newItem);
            m_svcContext.SaveChanges();
        }

        #endregion 


        #region Private Methods

        private MyServiceContext m_svcContext = null;
        
        #endregion
        
    }
}
